Knock Me Out

Thoughts, ideas, and discussion about Knockout.js

Using External jQuery Template Files With KnockoutJS

| Comments

Update: another good solution for this topic is to use the external template engine found here.

A question recently came up on the Knockout forums about how to get better design time support in Visual Studio when working with jQuery templates and how to incorporate external template files into Knockout projects. Here are three different takes on solving both of these problems that each build on the previous idea.

Level One – Load individual template files

  1. Store each template in a .html file without the script tags. You could use a different extension, but using .html or .htm will ensure that both the IDE will treat it properly and that the web server will serve it up without further configuration.
  2. Make an AJAX request for each template file that you need and inject it into the document inside of a script tag.
  3. Use a convention that the filename without the extension is the ID of the template (id attribute of the script tag).
  4. Make sure that all of the template files have been loaded prior to calling ko.applyBindings

Example

First we store our templates in individual .html files. For example, suppose I have templates for a read-only view and for an editable view of an item. My read-only template might be stored in a file called Templates/itemTmpl.html and look like:

1
2
3
4
5
6
7
<tr>
    <td data-bind="text: name"></td>
    <td class="buttons">
        <button data-bind="click: function() { viewModel.editItem($data); }">Edit</button>
        <button data-bind="click: function() { viewModel.deleteItem($data); }">Delete</button>
    </td>
</tr>

On my page load, I would load my templates using something like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$(function() {
    ensureTemplates(["itemTmpl", "editTmpl"]);
});

function ensureTemplates(list) {
    var loadedTemplates = [];
    ko.utils.arrayForEach(list, function(name) {
        $.get("Templates/" + name + ".html", function(template) {
            $("body").append("<script id=\"" + name + "\" type=\"text/html\">" + template + "<\/script>");
            loadedTemplates.push(name);
            if (list.length === loadedTemplates.length) {
                ko.applyBindings(viewModel);
            }
        });
    });
}

The ensureTemplates function will load each template that is required using our convention that the name of the file matches the id of the script. We inject the script tag into the document, as if it had been there the whole time. We keep track of how many have been loaded and in the success callback of the last one to load we call ko.applyBindings.

Note: We probably should consider some additional error handling here to make sure that we either retry loading any templates that fail or at least present the user with an error message if all of the templates fail to load.

Pros

  • Gives each page the flexibility to load only the templates that it needs
  • Gives good Intellisense and syntax highlighting in Visual Studio
  • No further configuration necessary

Cons

  • The requests are a bit chatty, as it makes individual requests for each file.
  • Have to coordinate waiting for all files to load.

Link to Level One jsFiddle

Level Two – Load a single file generated at build time

  1. As in Level One, store each template in a .html file.
  2. At build time, generate a single html file that includes all of your template files and wraps them in their script tags. Without being wrapped in their script tags or at least some identifiable container, we would not be able to easily understand where the templates start and end.
  3. Make an AJAX request for the single .html file and call ko.applyBindings in the success callback of that request.

Example

We would store our templates in the same manner, but then during our build we would produce a single file that contains all of our templates. Here is one way to produce such a file using MSBUILD for a .NET Web Application project.

Open your Web Application’s .csproj file. At the end of the file uncomment the AfterBuild task and make it look something like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<Target Name="AfterBuild">
    <Delete Files="Templates\all.html" />
    <ItemGroup>
      <Templates Include="Templates\*.html" />
    </ItemGroup>
    <MSBuild Projects="$(MSBuildProjectFile)"
           Properties="Template=%(Templates.Identity);Name=%(Templates.FileName)"
           Targets="PrepareOneTemplate">
    </MSBuild>
  </Target>
  <Target Name="PrepareOneTemplate">
    <PropertyGroup>
        <Prefix><![CDATA[<script id="$(Name)" type="text/html">
]]>
</Prefix>
         <Suffix><!<![CDATA[
</script>]]></Suffix>
        <Contents>$(Prefix)$([System.IO.File]::ReadAllText($(Template)))$(Suffix)</Contents>
        <AllFile>Templates\all.html</AllFile>
    </PropertyGroup>
    <WriteLinesToFile File="$(AllFile)" Lines="$(Contents)" Overwrite="false" />
  </Target>

On each build, we first delete our composite file that I named all.html. Then, we call a target named PrepareOneTemplate for each of our template files. The PrepareOneTemplate target simply builds a string that wraps the template file contents in an appropriate script tag and writes it to the composite file. The end result is a single file that contains all of the script tags for our templates.

There certainly are many ways that this file could be generated. I’m sure that most people could find a way to generate it as part of their build process, even by just calling some shell commands.

Pros

  • A single AJAX request is made for the templates.
  • Gives good Intellisense and syntax highlighting in Visual Studio, as you would still be always editing the individual .html template files.
  • Final project does not even need to include the individual .html files. If someone accidentally browsed to the composite .html file they would not see anything visible.

Cons

  • If we need to load different subsets of templates on each page, then we might have to do some work in the build to properly generate a variety of composite files.

Link to Level Two jsFiddle

Level Three – Reference a script generated at build time

  1. As in Level One, store each template in a .html file.
  2. At build time, generate a single .js file. This file would include JavaScript to inject a string representation of each template into the document.
  3. Reference the script on your page prior to your code that calls ko.applyBindings.

Example

This time, we can use the same technique as Level Two in MSBUILD, we just need to generate lines that look like:

1
jQuery("body").append("<script id=\"yourid\">yourtemplate</script>");

This could even just do a document.write with the script tag and it would work or append it without the jQuery. I just like appending it to the body, as if it had been there all along.

The MSBUILD task would look like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<Target Name="AfterBuild">
    <Delete Files="Templates\all.html;Templates\all.js" />
    <ItemGroup>
      <Templates Include="Templates\*.html" />
    </ItemGroup>
    <MSBuild Projects="$(MSBuildProjectFile)"
           Properties="Template=%(Templates.Identity);Name=%(Templates.FileName)"
           Targets="PrepareOneTemplate">
    </MSBuild>
  </Target>
  <Target Name="PrepareOneTemplate">
    <PropertyGroup>
        <Prefix><![CDATA[<script id="$(Name)" type="text/html">
 ]]></Prefix>
         <Suffix><![CDATA[
</script>]]></Suffix>
        <Contents>$(Prefix)$([System.IO.File]::ReadAllText($(Template)))$(Suffix)</Contents>
        <AllFileJS>Templates\all.js</AllFileJS>
        <ContentsJS>jQuery("body").append("$(Contents.Replace('%22','\%22').Replace('%0A','').Replace('%0D',''))%22)%3B</ContentsJS>
    </PropertyGroup>
    <WriteLinesToFile File="$(AllFileJS)" Lines="$(ContentsJS)"    Overwrite="false" />
  </Target>
</Project>

We again wrap the template file contents in a script tag. Then we replace all quotes with \” and strip any carriage returns or line feeds. Finally, we put in the JavaScript statement that injects the text into our document.

Again, I am sure that there are many ways that this file could be generated as well, possibly by processing the files with regular expressions.

Pros

  • No need to add code for making an AJAX request, as the script tag would handle it naturally.
  • Gives good Intellisense and syntax highlighting in Visual Studio, as you would still be always editing the individual .html template files.
  • Could pull templates cross-domain, although doubtful that it would be a normal scenario.
  • Final project does not even need to include the individual .html files.

Cons

  • If we need to load different subsets of templates on each page, then we might have to do some work in the build to properly generate several scripts.

Link to Level Three jsFiddle

Final Thoughts

So far, I am happiest with the Level Three method. All three methods give you a decent experience in the IDE, but the last one is easy to use on a page and the build process takes care of the work. Any other ideas/thoughts/methods for including external templates while getting a good design-time experience?

Comments